home *** CD-ROM | disk | FTP | other *** search
/ Tech Arsenal 1 / Tech Arsenal (Arsenal Computer).ISO / tek-04 / bipl.zip / PROGS.ZIP / IPP.ICN < prev    next >
Text File  |  1992-09-28  |  37KB  |  1,173 lines

  1. ###########################################################################
  2. #
  3. #    File:     ipp.icn
  4. #
  5. #    Subject:  Program to preprocess Icon programs
  6. #
  7. #    Author:   Robert C. Wieland, revised by Frank J. Lhota
  8. #
  9. #    Date:     June 12, 1991
  10. #
  11. ###########################################################################
  12. #
  13. #     Ipp is a preprocessor for the Icon language.  Ipp has many operations and
  14. #  features that are unique to the Icon environment and should not be used as
  15. #  a generic preprocessor (such as m4).  Ipp produces output which when written
  16. #  to a file is designed to be the source for icont, the command processor for
  17. #  Icon programs.
  18. #  
  19. #  Ipp may be invoked from the command line as:
  20. #
  21. #    ipp [option  ...] [ifile [ofile]]
  22. #  
  23. #     Two file names may be specified as arguments.  'ifile' and 'ofile' are 
  24. #  respectively the input and output files for the preprocessor.  By default
  25. #  these are standard input and standard output.  If the output file is to be
  26. #  specified while the input file should remain standard input a dash ('-')
  27. #  should be given as 'ifile'.  For example, 'ipp - test' makes test the output
  28. #  file while retaining standard input as the input file.
  29. #  
  30. #     The following special names are predefined by ipp and may not be
  31. #  redefined #  or undefined.  The name _LINE_ is defined as the line number
  32. #  (as an integer) of the line of the source file currently processed.  The
  33. #  name _FILE_ is defined as the name of the current source file
  34. #  (as a string).  If the source is standard input then it has the value
  35. #  'stdin'.
  36. #
  37. #     Ipp will also set _LINE_ and _FILE_ from the "#line" directives it
  38. #  encounters, and will insert line directives to indicate source origins.
  39. #  
  40. #     Also predefined are names corresponding to the features supported by the
  41. #  implementation of Icon at the location the preprocessor is run.  This allows
  42. #  conditional translations using the 'if' commands, depending on what features
  43. #  are available.  Given below is a list of the features on a 4.nbsd UNIX 
  44. #  implementation and the corresponding predefined names:
  45. #  
  46. #      Feature                Name
  47. #      -----------------------------------------------------
  48. #      UNIX                UNIX
  49. #      co-expressions            co_expressions
  50. #      overflow checking        overflow_checking
  51. #      direct execution        direct_execution
  52. #      environment variables        environment_variables
  53. #      error traceback            error_traceback
  54. #      executable images        executable_images
  55. #      string invocation        string_invocation
  56. #      expandable regions        expandable_regions
  57. #  
  58. #  
  59. #  Command-Line Options:
  60. #  ---------------------
  61. #  
  62. #    The following options to ipp are recognized:
  63. #  
  64. #   -C        By default ipp strips Icon-style comments.  If this option
  65. #         is specified all comments are passed along except those
  66. #         found on ipp command lines (lines starting with  a '$' 
  67. #         command).
  68. #   -D name    
  69. #   -D name=def    Allows the user to define a name on the command line instead
  70. #         of using a $define command in a source file.  In the first
  71. #         form the name is defined as '1'.  In the second form name is
  72. #         defined as the text following the equal sign.  This is less
  73. #         powerful than the $define command line since def can not
  74. #         contain any white space (spaces or tabs).
  75. #   -d depth    By default ipp allows include files to be nested to a depth
  76. #         of ten.  This allows the preprocessor to detect infinitely
  77. #         recursive include sequences.  If a different limit for the
  78. #         nesting depth is needed it may changed by using this option
  79. #         with an integer argument greater than zero. Also, if a file
  80. #         is found to already be in a nested include sequence an
  81. #         error message is written regardless of the limit.
  82. #   -I dir    The following algorithm is normally used in searching for
  83. #         $include files.  On a UNIX system names enclosed in "" are
  84. #         searched for by trying in order the directories specified by the
  85. #         PATH environment variable, and names enclosed in <> are always
  86. #         expected to be in the /usr/icon/src directory.  On other systems
  87. #         names enclosed in <> are seacrhed for by trying in order the
  88. #         directories specified by the IPATH environment variable; names
  89. #         in "" are serched for in a similar fashion, except that the
  90. #         current directory is tried first.  If the -I option is given the
  91. #         directory specified is searched before the 'standard'
  92. #          directories.  If this option is specified more than once the
  93. #          directories specified are tried in the order that they appear on
  94. #          the command line, then followed by the 'standard' directories.
  95. #  
  96. #  Preprocessor commands:
  97. #  ----------------------
  98. #  
  99. #     All ipp commands start with a line that has '$' as its first non-space
  100. #  chararcher.  The name of the command must follow the '$'.  White space
  101. #  (any number of spaces or tabs) may be used to separate the '$' and the
  102. #  command name.  Any line beginning with a '$' and not followed by a valid
  103. #  name will cause an error message to be sent to standard error and
  104. #  termination of the preprocessor.  If the command requires an argument then
  105. #  it must be separated from the command name by white space otherwise the
  106. #  argument will be considered part of the name and the result will likely
  107. #  produce an error.  In processing the $ commands ipp responds to exceptional
  108. #  conditions in one of two ways.  It may produce a warning and continue
  109. #  processing or produce an error message and terminate.  In both cases the
  110. #  message is sent to standard error.  With the exception of error conditions
  111. #  encountered during the processing of the command line, the messages normally
  112. #  include the name and line number of the source file at the point the
  113. #  condition was encountered.  Ipp was designed so that most exception
  114. #  conditions encountered will produce errors and terminate.  This protects the
  115. #  user since warnings could simply be overlooked or misinterpreted.
  116. #
  117. #     Many ipp command require names as arguments.  Names must begin with a
  118. #  letter or an underscore, which may be followed by any number of letters,
  119. #  underscores, and digits.  Icon-style comments may appear on ipp command
  120. #  lines, however they must be separated from the normal end of the command by
  121. #  white_space.  If any extraneous characters appear on a command line a
  122. #  warning is issued.  This occurs when characters other than white-space or a
  123. #  comment follow the normal end of a command.
  124. #  
  125. #     The following commands are implemented:
  126. #  
  127. #    $define:  This command may be used in one of two forms.  The first form
  128. #           only allows simple textual substitution.  It would be invoked as
  129. #          '$define name text'.  Subsequent occurrences of name are replaced 
  130. #          with text.  Name and text must be separated by one white space
  131. #          character which is not considered to be part of the replacement
  132. #          text.  Normally the replacement text ends at the end of the line.
  133. #          The text however may be continued on the next line if the backslash
  134. #          character '\' is the last character on the line.  If name occurs
  135. #          in the replacement text an error message (recursive textual substi-
  136. #          tution) is written.
  137. #  
  138. #          The second form is '$define name(arg,...,arg) text' which defines
  139. #          a macro with arguments.  There may be no white space between the 
  140. #          name and the '('.  Each occurrenceg of arg in the replacement text
  141. #          is replaced by the formal arg specified when the macro is 
  142. #          encountered.   When a macro with arguments is expanded the arguments
  143. #          are placed into the expanded replacement text unchanged.  After the
  144. #          entire replacement text is expanded, ipp restarts its scan for names
  145. #          to expand at the beginning of the newly formed replacement text.  
  146. #          As with the first form above, the replacement text may be continued
  147. #          on following lines.  The replacement text starts immediately after
  148. #          the ')'. 
  149. #          The names of arguments must comply with the convention for regular 
  150. #          names.  See the section below on Macro processing for more 
  151. #          information on the replacement process.
  152. #  
  153. #    $undef:   Invoked as '$undef name'.   Removes the definition of name.  If
  154. #          name is not a valid name or if name is one of the reserved names
  155. #          _FILE_ or _LINE_ a message is issued.
  156. #  
  157. #    $include: Invoked as '$include <filename>' or '$include "filename"'.  This
  158. #          causes the preprocessor to make filename the new source until
  159. #          end of file is reached upon which input is again taken from the
  160. #          original source.  See the -I option above for more detail.
  161. #  
  162. #    $dump:    This command, which has no arguments, causes the preprocessor to 
  163. #          write to standard error all names which are currently defined.
  164. #          See '$ifdef' below for a definition of 'defined'.
  165. #  
  166. #    $warning:
  167. #           This command issues a warning, with the text coming from the
  168. #        argument field of the command.
  169. #  
  170. #    $error:   This command issues a error, with the text coming from the
  171. #        argument field of the command.  As with all errors, processing
  172. #        is terminated.
  173. #  
  174. #    $ifdef:   Invoked as 'ifdef name'.  The lines following this command appear
  175. #          in the output only if the name given is defined.  'Defined' means
  176. #            1.  The name is a predefined name and was not undefined using
  177. #            $undef, or
  178. #            2.  The name was defined using $define and has not been undefined
  179. #            by an intervening $undef.
  180. #  
  181. #    $ifndef:  Invoked as 'ifndef name'.  The lines following this command do 
  182. #           not appear in the ouput if the name is not defined.
  183. #  
  184. #    $if:      Invoked as 'if constant-expression'.  Lines following this
  185. #           command are processed only if the constant-expression produces a
  186. #           result. The following arithmetic operators may be applied to
  187. #           integer arguments: + - * / % ^
  188. #
  189. #          If an argument to one of the above operators is not an integer an
  190. #          error is produced.
  191. #  
  192. #             The following functions are provided: def(name), ndef(name)
  193. #          This allows the utility of $ifdef and $ifndef in a $if command.
  194. #          def produces a result if name is defined and ndef produces a
  195. #          result if name is not defined.  
  196. #          
  197. #             The following comparision operators may be used on integer
  198. #           operands:
  199. #
  200. #          > >= = < <= ~=
  201. #
  202. #              Also provided are alternation (|), conjunction (&), and
  203. #           negation (not).  The following table lists all operators with
  204. #           regard to decreasing precedence:
  205. #  
  206. #        not + - (unary)
  207. #          ^ (associates right to left)
  208. #          * / %
  209. #          + - (binary)
  210. #               > >= = < <= ~=
  211. #          |
  212. #          &
  213. #  
  214. #          The precedence of '|' and '&' are the same as the corresponding
  215. #          Icon counterparts.  Parentheses may be used for grouping.
  216. #          Backtracking is performed, so that the expression
  217. #
  218. #          FOO = (1|2)
  219. #
  220. #          will produce a result precisely when FOO is either 1 or 2.
  221. #
  222. #    $elif:    Invoked as 'elif constant-expression'.  If the lines preceding
  223. #          this command were processed, this command and the lines following
  224. #           it up to the matching $endif command are ignored.  Otherwise,
  225. #           the constant-expression is evaluated, and the lines following this
  226. #          command are processed only if it produces a result.
  227. #  
  228. #    $else:    This command has no arguments and reverses the notion of the
  229. #           test command which matches this directive.  If the lines preceding
  230. #           this command where ignored the lines following are processed, and
  231. #           vice versa.
  232. #  
  233. #    $endif:   This command has no arguments and ends the section of lines
  234. #           begun by a test command ($ifdef, $ifndef, or $if).  Each test
  235. #           command must have a matching $endif.
  236. #  
  237. #  Macro Processing and Textual Substitution
  238. #  -----------------------------------------
  239. #     No substitution is performed on text inside single quotes (cset literals)
  240. #  and double quotes (strings) when a line is processed.   The preprocessor
  241. #  will #  detect unclosed cset literals or strings on a line and issue an
  242. #  error message unless the underscore character is the last character on the
  243. #  line.  The output from 
  244. #  
  245. #      $define foo bar
  246. #      write("foo")
  247. #  
  248. #  is
  249. #
  250. #       write("foo")
  251. #  
  252. #     Unless the -C option is specified comments are stripped from the source.
  253. #  Even if the option is given the text after the '#' is never expanded.
  254. #  
  255. #     Macro formal parameters are recognized in $define bodies even inside cset 
  256. #  constants and strings.  The output from
  257. #  
  258. #      $define test(a)        "a"
  259. #      test(processed)
  260. #  
  261. #  is the following sequence of characters: "processed".
  262. #  
  263. #     Macros are not expanded while processing a $define or $undef.  Thus:
  264. #  
  265. #      $define off invalid
  266. #      $define bar off
  267. #      $undef off
  268. #      bar
  269. #  
  270. #  produces off.  The name argument to $ifdef or $ifndef is also not expanded.
  271. #  
  272. #     Mismatches between the number of formal and actual parameters in a macro
  273. #  call are caught by ipp.  If the number of actual parameters is greater than
  274. #  the number of formal parameters is error is produced.  If the number of
  275. #  actual parameters is less than the number of formal parameters a warning is
  276. #  issued and the missing actual parameters are turned into null strings.
  277. #  
  278. ############################################################################
  279. #
  280. #    The records and global variables used by ipp are described below:
  281. #
  282. #  Src_desc:        Record which holds the 'file descriptor' and name
  283. #            of the corresponding file.  Used in a stack to keep
  284. #                track of the source files when $includes are used.
  285. #  Opt_rec         Record returned by the get_args() routine which returns
  286. #            the options and arguments on the command line.  options
  287. #            is a cset containing options that have no arguments.
  288. #            pairs is a list of [option,  argument] pairs. ifile and
  289. #            ofile are set if the input or output files have been
  290. #            specified.
  291. #  Defs_rec        Record stored in a table keyed by names.  Holds the
  292. #            names of formal arguments, if any, and the replacement
  293. #            text for that name.
  294. #  Expr_node        Node of a parse tree for $if / $elif expressions.
  295. #            Holds the operator, or a string representing the
  296. #            control strcture.  Also, holds a list of the args for
  297. #            the operation / control structure, which are either
  298. #            scalars or other Expr_node records.
  299. #  Chars        Cset of all characters that may appear in the input.
  300. #  Defs            The table holding the definition data for each name.
  301. #  Depth        The maximum depth of the input source stack.
  302. #  Ifile        Descriptor for the input file.
  303. #  Ifile_name        Name of the input file.
  304. #  Init_name_char     Cset of valid initial characters for names.
  305. #  Line_no        The current line number.
  306. #  Name_char        Cset of valid characters for names.
  307. #  Non_name_char    The complement of the above cset.
  308. #  Ofile        The descriptor of the output file.
  309. #  Options        Cset of no-argument options specified on the command
  310. #            line.
  311. #  Path_list        List of directories to search in for "" include files.
  312. #  Src_stack        The stack of input source records.
  313. #  Std_include_paths    List of directories to search in for <> include files.
  314. #  White_space        Cset for white-space characters.
  315. #  TRUE            Defined as 1.
  316. #
  317. ############################################################################
  318.  
  319. record Src_desc(fd, fname, line)
  320. record Opt_rec(options, pairs, ifile, ofile)
  321. record Defs_rec(arg_list, text)
  322. record Expr_node(op, arg)
  323.  
  324. global Chars, Defs, Depth, Ifile, Ifile_name, Init_name_char, 
  325.   Line_no, Name_char, Non_name_char, Ofile, Options, Path_list, 
  326.   Src_stack, Std_include_paths, White_space, TRUE, DIR_SEP
  327.  
  328. procedure main(arg_list)
  329.   local line, source
  330.  
  331.   init(arg_list)
  332.  
  333.   repeat {
  334.     while line := get_line(Ifile) do
  335.       line ? process_cmd(get_cmd())
  336.  
  337.     # Get new source
  338.     close(Ifile)
  339.     if source := pop(Src_stack) then {
  340.       Ifile := source.fd
  341.       Ifile_name := source.fname
  342.       Line_no := source.line
  343.       }
  344.     else  break
  345.   }
  346. end
  347.  
  348. procedure conditional(expr)
  349.  
  350.   return if eval(expr) then
  351.       true_cond()
  352.     else
  353.       false_cond()
  354. end
  355.  
  356. #
  357. # In order to simplify the parsing the four operators that are longer
  358. # than one character (<= ~= >= not) are replaced by one character
  359. # 'aliases'.  Also, all white space is removed.
  360. #
  361.  
  362. procedure const_expr(expr)
  363.   local new
  364.  
  365.   static White_space_plus
  366.  
  367.   initial White_space_plus := White_space ++ '<>~n'
  368.  
  369.   new := ""
  370.   expr ? {
  371.     while new ||:= tab(upto(White_space_plus)) ||
  372.       if any(White_space) then {
  373.         tab(many(White_space))
  374.      ""
  375.     }
  376.       else if =">=" then "\x01"
  377.       else if ="<=" then "\x02"
  378.       else if ="~=" then "\x03"
  379.       else if not any(Name_char, ,&pos - 1) &
  380.               ="not" &
  381.               not any(Name_char) then "\x04"
  382.       else move (1)
  383.     new ||:= tab(0)
  384.     }
  385.   #
  386.   # Now recursively parse the transformed string.
  387.   #
  388.   return parse(new)
  389.  
  390. end
  391.  
  392. procedure decoded(op)
  393.   return case op of {
  394.     "\x01":     ">="
  395.     "\x02":     "<="
  396.     "\x03":     "~="
  397.     "\x04":     "not"
  398.     default:     op
  399.     }
  400. end
  401.  
  402. procedure def_opt(s)
  403.   local name, text
  404.  
  405.   s ? {
  406.     name := tab(find("=")) | tab(0)
  407.     text := (move(1) & tab(0)) | "1"
  408.     }
  409.   if name == ("_LINE_" | "_FILE_") then
  410.     error(name, " is a reserved name and can not be redefined by the -D option")
  411.   if not name ? (get_name() & pos(0)) then
  412.     error(name, " :  Illegal name argument to -D option")
  413.   if member(Defs, name) then
  414.     warning(name, " : redefined by -D option")
  415.   insert(Defs, name, Defs_rec(, text))
  416. end
  417.  
  418. procedure define()
  419.   local args, name, text
  420.  
  421.   get_opt_ws()
  422.   if name := get_name() & (any(White_space ++ '(') | pos(0)) then {
  423.     if name == ("_LINE_" | "_FILE_") then
  424.       error(name, " is a reserved name and can not be redefined")
  425.  
  426.     if match("(") then             # A macro
  427.       args := get_formals()
  428.     text := get_text(args)
  429.  
  430.     if member(Defs,name) then
  431.       warning(name, " redefined")
  432.     insert(Defs, name, Defs_rec(args, text))
  433.     }  
  434.   else
  435.     error("Illegal or missing name in define")
  436. end
  437.  
  438. procedure dump()
  439.   if not pos(0) then
  440.     warning("Extraneous characters after dump command")
  441.   every write(&errout, (!sort(Defs))[1])
  442. end
  443.  
  444. procedure error(s1, s2)
  445.   s1 ||:= \s2
  446.   stop(Ifile_name, ":  ", Line_no, ":  ", "Error  ", s1)
  447. end
  448.  
  449. procedure eval(node)
  450.   suspend case type(node) of {
  451.     "Expr_node": {
  452.       case node.op of {
  453.     "|"     : eval(node.arg[1]) | eval(node.arg[2])
  454.     "&"     : eval(node.arg[1]) & eval(node.arg[2])
  455.     "not"   : not eval(node.arg[1])
  456.     "def"   : member(Defs, node.arg[1])
  457.     "ndef"  : not member(Defs, node.arg[1])
  458.     default :
  459.       case *node.arg of {
  460.         1 : node.op(eval(node.arg[1]))
  461.         2 : node.op(eval(node.arg[1]), eval(node.arg[2]))
  462.         }
  463.      }
  464.        }
  465.     default: node
  466.     }       
  467. end
  468.  
  469. procedure false_cond()
  470.   local cmd, line
  471.  
  472.   # Skip to next $else / $elif branch, or $endif
  473.   cmd := skip_to("elif", "else", "endif")
  474.   case cmd of {
  475.     "elif" : return if_cond(cmd)
  476.     "else" : {
  477.        while line := get_line(Ifile) do
  478.       line ? {
  479.         cmd := get_cmd()
  480.         case cmd of {
  481.           "elif"  :
  482.         error("'elif' encountered after 'else'")
  483.           "else"  :
  484.         error("multiple 'else' sections")
  485.           "endif" : return
  486.           default : process_cmd(cmd)
  487.           }
  488.         }
  489.        error("'endif' not encountered before end of file")
  490.        }
  491.     "endif": return
  492.     }
  493. end
  494.     
  495. procedure find_file(fname, path_list)
  496.   local ifile, ifname, path 
  497.  
  498.   every path := !path_list do {
  499.     ifname :=
  500.       if path == ("" | ".") then
  501.     fname
  502.       else
  503.     path || DIR_SEP || fname
  504.  
  505.  
  506.     if ifile := open(ifname) then {
  507.       if *Src_stack >= Depth then {
  508.     close(ifile)
  509.     error("Possibly infinitely recursive file inclusion")
  510.     }
  511.       if ifname == (Ifile_name | (!Src_stack).fname) then
  512.     error("Infinitely recursive file inclusion")
  513.       push(Src_stack, Src_desc(Ifile, Ifile_name, Line_no))
  514.       Ifile := ifile
  515.       Ifile_name := ifname
  516.       Line_no := 0
  517.       return
  518.       }
  519.     }
  520.   error("Can not open include file ", fname)
  521. end
  522.  
  523. procedure func(expr)
  524.   local op, arg
  525.  
  526.   expr ? {
  527.     if op  := tab(find("(")) & move(1) &
  528.        arg := get_name() & =")" & pos(0) then {
  529.       if op == ("def" | "ndef") then
  530.     return Expr_node(op, [arg])
  531.       else    
  532.         error("Invalid function name") 
  533.       }
  534.     }
  535. end
  536.  
  537. procedure get_args(arg_list, simple_opts, arg_opts)
  538.   local arg, ch, get_ofile, i, opts, queue
  539.   opts := Opt_rec('', [])
  540.   queue := []
  541.  
  542.   every arg := arg_list[i := 1 to *arg_list] do
  543.     if arg == "-" then         # Next argument should be output file
  544.       get_ofile := (i = *arg_list - 1) | 
  545.     stop("Invalid position of '-' argument")
  546.     else if arg[1] == "-" then     # Get options
  547.       every ch := !arg[2: 0] do
  548.     if any(simple_opts, ch) then
  549.       opts.options ++:= ch
  550.     else if any(arg_opts, ch) then
  551.       put(queue, ch)
  552.     else
  553.       stop("Invalid option - ", ch)
  554.     else if ch := pop(queue) then     # Get argument for option
  555.       push(opts.pairs, [ch, arg])
  556.     else if \get_ofile then {     # Get output file
  557.       opts.ofile := arg
  558.       get_ofile := &null
  559.       }
  560.     else {            # Get input file
  561.       opts.ifile := arg
  562.       get_ofile := (i < *arg_list)
  563.       }
  564.  
  565.   if \get_ofile | *queue ~= 0 then
  566.     stop("Invalid number of arguments")
  567.  
  568.   return opts
  569. end
  570.  
  571. procedure get_cmd()
  572.   local cmd
  573.   static  no_arg_cmds
  574.   initial no_arg_cmds := set(["dump", "else", "endif"])
  575.  
  576.   if ="#" & cmd := ="line" then
  577.     get_opt_ws()
  578.   else if (get_opt_ws()) & ="$" then {
  579.     get_opt_ws()
  580.     (cmd := tab(many(Chars))) | error("Missing command")
  581.     get_opt_ws()
  582.     if not pos(0) & member(no_arg_cmds, cmd) then
  583.       warning("Extraneous characters after argument to '" || cmd || "'")
  584.     }
  585.   else
  586.     tab (1)
  587.   return cmd
  588. end
  589.  
  590. procedure get_formals()
  591.   local formal, arglist, ch
  592.  
  593.   arglist := []
  594.   ="("
  595.   get_opt_ws()
  596.   if not =")" then 
  597.     repeat {
  598.       if (formal := get_name()) & get_opt_ws() & any(',)') then
  599.     put(arglist, formal)
  600.       else    
  601.     error("Invalid formal argument in macro definition")
  602.       if =")" then break
  603.       =","
  604.       get_opt_ws()
  605.       }
  606.   get_opt_ws()
  607.   return arglist
  608. end
  609.  
  610. procedure get_line(Ifile)
  611.   return 1(read(Ifile), Line_no +:= 1)
  612. end
  613.  
  614. procedure get_name()
  615.   return tab(any(Init_name_char)) || (tab(many(Name_char)) | "")
  616. end
  617.  
  618. procedure get_opt_ws()
  619.   return (tab(many(White_space)) | "") || (="#" || tab(0) | "")
  620. end
  621.  
  622. procedure get_text(is_macro)
  623.   local text
  624.  
  625.   if \is_macro then
  626.     text := tab(0)
  627.   else
  628.     text := (tab(any(White_space)) & tab(0)) | ""
  629.   while (text[-1] == "\\") do
  630.     (text := text[1:-1] || get_line(Ifile)) |
  631.       error("Continuation line not found before end of file")
  632.   return text
  633. end
  634.  
  635. # if_cond is the procedure for $if or $elif.  
  636. #
  637. # Procedure true_cond is invoked if the evaluation of a previous $if, $ifdef, or
  638. # $ifndef causes subsequent lines to be processed.  Lines will be processed
  639. # upto an $elif, $else, or $endif.  If $elif or $else is encountered, lines
  640. # are skipped until the matching $endif is encountered.
  641. #
  642. # Procedure false_cond is invoked if the evaluation of a previous $if, $ifdef, 
  643. # or $ifndef causes subsequent lines to be skipped.  Lines will be skipped 
  644. # upto an $elif, $else, or, $endif.  If $else is encountered, lines are
  645. # processed until the $endif matching the $else is encountered.
  646.  
  647. procedure if_cond(cmd)
  648.   if pos(0) then
  649.     error("Constant expression argument to '" || cmd || "' missing")
  650.   else
  651.     return conditional(const_expr(tab(0)))
  652. end
  653.  
  654. procedure ifdef()
  655.   local name
  656.  
  657.   if name := get_name() then
  658.     {
  659.     get_opt_ws()
  660.     if not pos(0) then
  661.       warning("Extraneous characters after argument to 'ifdef'")
  662.     return conditional(Expr_node("def", [name]))
  663.     }
  664.   else
  665.     error("Argument to 'ifdef' is not a valid name")
  666. end
  667.   
  668. procedure ifndef()
  669.   local name
  670.  
  671.   if name := get_name() then {
  672.     get_opt_ws()
  673.     if not pos(0) then
  674.       warning("Extraneous characters after argument to 'ifndef'")
  675.     return conditional(Expr_node("ndef", [name]))
  676.     }
  677.   else
  678.     error("Argument to 'ifndef' is not a valid name")
  679. end
  680.  
  681. procedure in_text(name, text)
  682.   return text ? 
  683.     tab(find(name)) &
  684.     (if move(-1) then tab(any(Non_name_char)) else "") &
  685.     move(*name) &
  686.     (tab(any(Non_name_char)) | pos(0))
  687. end
  688.  
  689. procedure include()
  690.   local ch, fname 
  691.   static fname_chars, stopper
  692.  
  693.   initial {
  694.     fname_chars := Chars -- '<>"'
  695.     stopper := table()
  696.     insert(stopper, "\"", "\"")
  697.     insert(stopper, "<",  ">")
  698.     }
  699.  
  700.   if (ch    := tab(any('"<'))) &
  701.      (fname := tab(many(fname_chars))) &
  702.       =stopper[ch] then {
  703.     get_opt_ws()
  704.     if not pos(0) then
  705.       warning("Extraneous characters after include file name")
  706.     find_file(fname,
  707.       case ch of {
  708.     "\"" : Path_list
  709.     "<"  : Std_include_paths
  710.     }
  711.       )
  712.     }
  713.   else
  714.     error("Missing or invalid include file name")
  715. end
  716.  
  717. procedure init(arg_list)
  718.   local s
  719.  
  720.   TRUE := 1
  721.   Defs := table()
  722.   Init_name_char := &letters ++ '_'
  723.   Name_char := Init_name_char ++ &digits
  724.   Non_name_char := ~Name_char
  725.   White_space := ' \t\b'
  726.   Chars := &ascii -- White_space
  727.   Line_no := 0
  728.   Depth := 10
  729.  
  730.   # Predefine features
  731.   every s := &features do {
  732.     s := map(s, " -/", "___")
  733.     insert(Defs, s, Defs_rec(, "1"))
  734.     }
  735.  
  736.   # Set path list for $include files given in "", <>
  737.   if member(Defs, "UNIX") then {
  738.     Path_list := []
  739.     getenv("PATH") ? while put(Path_list, 1(tab(find(":")), move(1)))
  740.     Std_include_paths := ["/usr/icon/src"]
  741.     }
  742.   else {
  743.     Std_include_paths := []
  744.     (getenv("IPATH") || " ") ?
  745.        while put(Std_include_paths, tab(find(" "))) do move(1)
  746.     Path_list := [""] ||| Std_include_paths
  747.     }
  748.  
  749.   process_options(arg_list)
  750. end
  751.  
  752. procedure lassoc(expr, op)
  753.   local j, arg1, arg2
  754.  
  755.   expr ? {
  756.     every j := bal(op)
  757.     # Succeeds if op found.
  758.     if arg1 := tab(\j) & op := decoded(move(1)) & arg2 := tab(0) then {
  759.       op := proc(op, 2)        # Fails for control structures
  760.       return Expr_node(op, [parse(arg1), parse(arg2)])
  761.       }
  762.     }
  763. end
  764.  
  765. #
  766. # Programmer's note: Ifile_name and Line_no should not be assigned new
  767. # values until the very end, so that if there is an error, the error
  768. # message will include the file/line no of the current line directive,
  769. # instead of the file/line of the text that follows the directive.
  770. #
  771. procedure line()
  772.   local new_line, new_file
  773.  
  774.   new_line := tab(many(&digits)) | error("No line number in line directive")
  775.   get_opt_ws()
  776.   if ="\"" then {
  777.     new_file := ""
  778.     #
  779.     # Get escaped chars.  We assume that the only escaped chars
  780.     # appearing in a file name would be \\ or \", where the actual
  781.     # character to be used is simply the character following the slash.
  782.     # In the unlikely event that other escape sequences are encountered,
  783.     # this section would have to revised.
  784.     #
  785.     while new_file ||:= tab(find("\\")) || (move(1) & move(1))
  786.     new_file ||:= tab(find("\"")) |
  787.       error("Invalid file name in line directive")
  788.     }
  789.  
  790.   Line_no    := integer(new_line)
  791.   Ifile_name := \new_file
  792.   return
  793. end
  794.  
  795. procedure macro_call(entry, args)
  796.   local i, value, result, token
  797.  
  798.   value := table()
  799.   every i := 1 to *entry.arg_list do
  800.     insert(value, entry.arg_list[i], args[i] | "")
  801.  
  802.   entry.text ? {
  803.     result := tab(upto(Name_char) | 0)
  804.     while token := tab(many(Name_char)) do {
  805.       result ||:= \value[token] | token
  806.       result ||:= tab(many(Non_name_char))
  807.       }
  808.     }
  809.   return result
  810. end
  811.  
  812. procedure no_endif_error()
  813.   error("'endif' not encountered before end of file")
  814. end
  815.  
  816. procedure parse(expr)
  817.   # strip surrounding parens.
  818.   while expr ?:= 2(="(", tab(bal (')')), pos(-1))
  819.  
  820.   return lassoc(expr, '&' | '|') |
  821.     lassoc(expr, '<=>\x01\x02\x03' | '+-' | '*/%') |
  822.     rassoc(expr, '^') | 
  823.     unary(expr, '+-\x04') |
  824.     func(expr) |
  825.     integer(process_text(expr)) |
  826.     error(expr, " :  Integer expected")
  827. end
  828.  
  829. procedure process_cmd(cmd)
  830.   static last_cmd
  831.   initial last_cmd := ""
  832.  
  833.   case cmd of {
  834.     "dump"    : dump()
  835.     "define"  : define()
  836.     "undef"   : undefine()
  837.     "include" :    include()
  838.     "line"    : line()
  839.     "error"   :    error(tab(0))
  840.     "warning" :    warning(tab(0))
  841.     "if"      : if_cond( last_cmd := cmd )
  842.     "ifdef"   : ifdef(   last_cmd := cmd )
  843.     "ifndef"  : ifndef(  last_cmd := cmd )
  844.     "elif"   |
  845.     "else"   |
  846.     "endif"   :    error("No previous 'if' expression")
  847.     &null     : {
  848.       if \last_cmd then
  849.     put_linedir(Ofile, Line_no, Ifile_name)
  850.       write(Ofile, process_text(tab(0)))
  851.       }
  852.     default   :    error("Undefined command")
  853.     }
  854.   last_cmd := cmd
  855.   return
  856. end
  857.  
  858. procedure process_macro(name, entry, s)
  859.   local arg, args, new_entry, news, token
  860.  
  861.   s ? {
  862.     args := []
  863.     if ="(" then {
  864.       #
  865.       # Get args if list is not empty.
  866.       #
  867.       get_opt_ws ()
  868.       if not =")" then
  869.     repeat {
  870.       arg := get_opt_ws()
  871.       if token := tab(many(Chars -- '(,)')) then {
  872.         if /(new_entry := Defs[token]) then
  873.           arg ||:= token
  874.         else if /new_entry.arg_list then
  875.           arg ||:= new_entry.text
  876.         else {  # Macro with arguments
  877.           if news := tab(bal(White_space ++ ',)')) then
  878.         arg ||:= process_macro(token, new_entry, news)
  879.           else
  880.         error(token, ":  Error in arguments to macro call")
  881.           } # if
  882.         } # if
  883.       else if not any(',)') then
  884.         error(name, ":  Incomplete macro call")
  885.       arg ||:= tab(many(White_space))
  886.       put(args, arg)
  887.       if match(")") then
  888.         break
  889.       move(1)
  890.     } # repeat 
  891.       if *args > *entry.arg_list then
  892.     error(name, ":  Too many arguments in macro call")
  893.       else if *args < *entry.arg_list then
  894.     warning(name, ":  Missing arguments in macro call")
  895.       return macro_call(entry, args)
  896.       } # if
  897.     }
  898. end
  899.  
  900. procedure process_options(arg_list)
  901.   local args, arg_opts, pair, simple_opts, tmp_list, value
  902.  
  903.   simple_opts := 'C'
  904.   arg_opts := 'dDI'
  905.   Src_stack := []
  906.  
  907.   args := get_args(arg_list, simple_opts, arg_opts)
  908.   if \args.ifile then {
  909.     (Ifile := open(args.ifile)) | stop("Can not open input file ", args.ifile)
  910.     Ifile_name := args.ifile
  911.     }
  912.   else {
  913.     Ifile := &input
  914.     Ifile_name := "stdin"
  915.     }
  916.   if \args.ofile then 
  917.     (Ofile := open(args.ofile, "w")) | stop("Can not open output file",
  918.       args.ofile)
  919.   else 
  920.     Ofile := &output
  921.  
  922.   Options := args.options 
  923.   tmp_list := []
  924.   every pair := !args.pairs do
  925.     case pair[1] of {
  926.       "D":    def_opt(pair[2])
  927.       "d":    if (value := integer(pair[2])) > 0 then
  928.           Depth := value
  929.         else
  930.           stop("Invalid argument for depth")
  931.       "I":    push(tmp_list, pair[2])
  932.     }
  933.   Path_list := tmp_list ||| Path_list
  934. end
  935.  
  936. procedure process_text(line)
  937.   local add, entry, new, position, s, token
  938.   static in_string, in_cset
  939.  
  940.   new :=  ""
  941.   while *line > 0 do {
  942.     add := ""
  943.     line ?:= {
  944.       if \in_string then {
  945.     # Ignore escaped chars
  946.     while new ||:= tab(find("\\")) || move(2)
  947.     if new ||:= tab(find("\"")) || move(1) then
  948.       in_string := &null
  949.     else {
  950.       new ||:= tab(0)
  951.       if line[-1] ~== "_" then {
  952.         in_string := &null
  953.         warning("Unclosed double quote")
  954.         }
  955.       }
  956.     }        
  957.       else if \in_cset then {
  958.     # Ignore escaped chars.
  959.     while new ||:= tab(find("\\")) || move(2)
  960.     if new ||:= (tab(find("'")) || move(1)) then
  961.       in_cset := &null
  962.     else {
  963.       new ||:= tab(0)
  964.       if line[-1] ~== "_" then {
  965.         in_cset := &null
  966.         warning("Unclosed single quote")
  967.         }
  968.       }
  969.     }   
  970.  
  971.       new ||:= tab(many(White_space))
  972.       case token := tab(many(Name_char) | any(Non_name_char)) of {
  973.     "\"": {
  974.       new ||:= "\""
  975.       if \in_string then 
  976.         in_string := &null
  977.       else if not pos(0) then {
  978.         in_string := TRUE 
  979.         }
  980.       else {
  981.         warning("Unclosed double quote")
  982.         }
  983.       add ||:= tab(0)
  984.         }
  985.     "'": {
  986.       new ||:= "'"
  987.       if \in_cset then 
  988.         in_cset := &null
  989.       else if not pos(0) then {
  990.         in_cset := TRUE 
  991.         }
  992.       else {
  993.         warning("Unclosed double quote")
  994.         }
  995.       add ||:= tab(0)
  996.         }
  997.     "#": {
  998.         new ||:= if any(Options, 'C') then token || tab(0)
  999.         else tab(0) & token ? tab(find("#"))
  1000.         }
  1001.     "__LINE__":
  1002.       new ||:= Line_no
  1003.     "__FILE__":
  1004.       new ||:= Ifile_name
  1005.     default: {
  1006.       if /(entry := Defs[token]) then
  1007.         new ||:= token
  1008.       else if /entry.arg_list then
  1009.         if in_text(token, entry.text) then
  1010.         error("Recursive textual substitution")
  1011.         else
  1012.         add := entry.text
  1013.       else {  # Macro with arguments
  1014.         s := tab(bal(White_space) | 0)
  1015.         if not any('(', s) then
  1016.         error(token, ":  Incomplete macro call")
  1017.         add := process_macro(token, entry, s)
  1018.         }
  1019.       } # default
  1020.     } # case
  1021.       add || tab(0)
  1022.       } # ?:=
  1023.     } # while
  1024.   return new
  1025. end
  1026.  
  1027. procedure put_linedir(Ofile, Line_no, Ifile_name)
  1028.   static last_filename
  1029.   initial last_filename := ""
  1030.  
  1031.   writes(Ofile, "#line ", Line_no - 1)
  1032.   #
  1033.   # Output file name part only if the
  1034.   # filename differs from the last one used.
  1035.   #
  1036.   if last_filename ~==:= Ifile_name then
  1037.     writes(Ofile, " ", image(last_filename))
  1038.   write(Ofile)
  1039.   return
  1040. end
  1041.  
  1042. procedure rassoc(expr, op)
  1043.   local arg1, arg2
  1044.  
  1045.  
  1046.   # Succeeds if op found.
  1047.   expr ? if arg1 := tab(bal(op)) & op := move(1) & arg2 := tab(0) then {
  1048.       op := decoded(op)
  1049.       op := proc(op, 2)        # Fails for control structures
  1050.       return Expr_node(op, [parse(arg1), parse(arg2)])
  1051.       }
  1052. end
  1053.  
  1054. #
  1055. # skip_to is used to skip over parts of the an '$if' structure. targets
  1056. # are the $if - related commands to skip to, and should always include
  1057. # "endif".
  1058. #
  1059. # We do not, of course, wish to skip to a command in an $if structure
  1060. # that is embedded in the current one; also, we want to make sure that
  1061. # embedded $if structures, even in skipped lines, are well formed.  We
  1062. # therefore maintain a stack, if_sects, of the currently applicable $if
  1063. # structure commands encountered in the skipped lines.  For example, if
  1064. # we have skipped over the commands
  1065. #
  1066. #    $ifdef ...
  1067. #       $if ...
  1068. #       $elif ...
  1069. #           $if ...
  1070. #           $else
  1071. #
  1072. # if_sect would be ["else", "elif", "ifdef"].
  1073. #
  1074. procedure skip_to(targets[])
  1075.   local cmd, if_sects, line, argpos
  1076.  
  1077.   if_sects := []
  1078.   while line := get_line(Ifile) | no_endif_error () do
  1079.     line ? {
  1080.       cmd := get_cmd()
  1081.       if *if_sects = 0 & \cmd == !targets then {
  1082.     argpos := &pos
  1083.     break
  1084.     }
  1085.  
  1086.       case cmd of {
  1087.     "if"    |
  1088.     "ifdef" |
  1089.     "ifndef" : {
  1090.       if pos(0) then
  1091.         error("Argument to '" || cmd || "' missing")
  1092.       push(if_sects, cmd)
  1093.       }
  1094.     "elif"   : {
  1095.       if pos(0) then
  1096.         error("Argument to '" || cmd || "' missing")
  1097.       if if_sects[1] == "else" then
  1098.         error("'elif' encountered after 'else'")
  1099.       else
  1100.         if_sects[1] := cmd
  1101.       }
  1102.     "else"   : {
  1103.       if if_sects[1] == "else" then
  1104.         error("multiple 'else' sections")
  1105.       else
  1106.         if_sects[1] := cmd
  1107.       }
  1108.     "endif"  : pop(if_sects)
  1109.     }
  1110.       }
  1111.  
  1112.   #
  1113.   # Now reset the &subject to the current line, and &pos to the argument
  1114.   # field of the current line, so that if we skipped to a line which will
  1115.   # require further processing (such as $elif), the scanning functions can
  1116.   # be used.
  1117.   #
  1118.   &subject := line
  1119.   &pos     := argpos
  1120.   return cmd
  1121.  
  1122. end
  1123.  
  1124. procedure true_cond()
  1125.   local cmd, line
  1126.  
  1127.   while line := get_line(Ifile) | no_endif_error () do
  1128.     line ? {
  1129.       case cmd := get_cmd() of {
  1130.     "elif" |
  1131.     "else"  : return skip_to("endif")
  1132.     "endif" : return cmd
  1133.     default : process_cmd(cmd)
  1134.     }
  1135.       }
  1136.      
  1137. end
  1138.  
  1139. procedure unary(expr, op)
  1140.   local arg1
  1141.  
  1142.  
  1143.   # Succeeds if op found.
  1144.   expr ?
  1145.     if op := decoded(tab(any(op))) & arg1 := tab(0) then {
  1146.       op := proc(op, 1)        # fails for control structures
  1147.       return Expr_node(op, [parse(arg1)])
  1148.       }
  1149. end
  1150.  
  1151. procedure undefine()
  1152.   local name
  1153.  
  1154.   if name := get_name() then {
  1155.     get_opt_ws()
  1156.     if not pos(0) then
  1157.       warning("Extraneous characters after argument to undef")
  1158.     if name == ("_LINE_" | "_FILE_") then
  1159.       error(name, " is a reserved name that can not be undefined")
  1160.     delete(Defs, name)
  1161.     }
  1162.   else
  1163.     error("Name missing in undefine")
  1164. end
  1165.  
  1166. procedure warning(s1, s2)
  1167.   s1 ||:= \s2
  1168.   write(&errout, Ifile_name, ":  ", Line_no, ":  ", "Warning  " || s1)
  1169. end
  1170.